#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import os

from common.cleaner import clear
from common.common import clean_dir
from common.common_models import CopyInfoRepModel
from common.const import RepositoryDataTypeEnum, RepoProtocalType, BackupTypeEnum
from common.exception.common_exception import ErrCodeException
from common.file_common import change_path_permission
from common.job_const import ParamKeyConst
from common.parse_parafile import ParamFileUtil
from common.util.check_utils import check_repo_path
from common.util.cmd_utils import cmd_format
from common.util.exec_utils import exec_mkdir_cmd
from gaussdbt.single import LOGGER
from gaussdbt.single.comm.gst_single_const import GstSingleKeyConst
from gaussdbt.single.comm.gst_single_error_code import ErrorCode
from gaussdbt.single.comm.utils.gst_single_backup_utils import GstSingleBackupUtil
from gaussdbt.single.comm.utils.gst_single_comm_utils import GstSingleCommonUtil
from gaussdbt.single.comm.utils.gst_single_param_utils import GstSingleParamUtil
from gaussdbt.single.comm.utils.gst_zbackup_utils import GstZbackupUtil


class GstSingleBackupService:
    def __init__(self, pid, job_id, sub_job_id, param_dict, os_user):
        self.pid = pid
        self.job_id = job_id
        self.sub_job_id = sub_job_id
        self.param_dict = param_dict
        self._os_user = os_user

    @staticmethod
    def build_copy_repositories(param_dict, backup_type, bak_dir_name):
        """构建副本持久仓信息"""
        repo_type = RepositoryDataTypeEnum.LOG_REPOSITORY.value if backup_type == BackupTypeEnum.LOG_BACKUP.value \
            else RepositoryDataTypeEnum.DATA_REPOSITORY.value
        copy_repo_info = ParamFileUtil.get_rep_info(param_dict.get("job"), repo_type)
        remote_path = os.path.realpath(os.path.join(copy_repo_info.get('remotePath'), bak_dir_name))
        repo_models = [
            CopyInfoRepModel(
                id=copy_repo_info.get('id'), repositoryType=copy_repo_info.get("repositoryType"),
                isLocal=copy_repo_info.get("isLocal"), protocol=RepoProtocalType.NFS.value,
                remotePath=remote_path, remoteHost=copy_repo_info.get("remoteHost"),
                extendInfo=copy_repo_info.get('extendInfo')
            )
        ]
        return repo_models

    @staticmethod
    def create_backup_dir(os_user, backup_dir):
        chk_ret = check_repo_path(backup_dir)
        if not chk_ret:
            LOGGER.error("The backup directory(%s) is invalid when creating.", backup_dir)
            raise ErrCodeException(ErrorCode.PARAMS_IS_INVALID, message="The backup directory is invalid.")
        if not os.path.exists(backup_dir):
            exec_mkdir_cmd(backup_dir)
            change_path_permission(backup_dir, os_user)
            LOGGER.info("Create backup directory success, os user: %s.", os_user)
        else:
            clean_dir(backup_dir)
            LOGGER.info("Clear backup directory success")

    def check_instance_status_for_backup(self):
        gsdb_home = GstSingleCommonUtil.get_val_of_gsdb_env_var(self._os_user, "GSDB_HOME")
        gsdb_data = GstSingleCommonUtil.get_val_of_gsdb_env_var(self._os_user, "GSDB_DATA")
        enable_login, sys_pwd = GstSingleBackupUtil.get_enable_sysdba_login_info(self.pid, self.param_dict)
        try:
            GstSingleCommonUtil.check_gsdb_status(
                self._os_user, gsdb_home, gsdb_data, enable_sysdba_login=enable_login, sys_pwd=sys_pwd)
        finally:
            clear(sys_pwd)

    def check_log_mode_for_backup(self):
        enable_login, sys_pwd = GstSingleBackupUtil.get_enable_sysdba_login_info(self.pid, self.param_dict)
        sql = "SELECT LOG_MODE from DV_DATABASE;"
        try:
            out = GstSingleCommonUtil.exec_gsdb_sql(
                self._os_user, sql, enable_sysdba_login=enable_login, sys_pwd=sys_pwd)
        finally:
            clear(sys_pwd)
        for i in out.split("\n"):
            if i.strip().upper() == "ARCHIVELOG":
                return
        raise ErrCodeException(ErrorCode.LOG_MODE_NOT_SATISFIED, message="The database log mode is not archivelog.")

    def handle_full_backup(self, bak_dir) -> dict:
        """处理全量备份"""
        gsdb_home = GstSingleCommonUtil.get_val_of_gsdb_env_var(self._os_user, "GSDB_HOME")
        gsdb_data = GstSingleCommonUtil.get_val_of_gsdb_env_var(self._os_user, "GSDB_DATA")
        zbackup_file = os.path.realpath(os.path.join(gsdb_home, "bin", "zbackup.py"))
        GstSingleCommonUtil.check_params(
            check_users=[self._os_user], check_paths=[zbackup_file, gsdb_data],
            check_path_owners=[(zbackup_file, [self._os_user])])
        enable_login, sys_pwd = GstSingleBackupUtil.get_enable_sysdba_login_info(self.pid, self.param_dict)
        try:
            if enable_login:
                full_bak_cmd = cmd_format("python3 {} -t full-backup -A {} -D {} -B {}", zbackup_file, gsdb_home,
                                          gsdb_data, bak_dir)
                ret, out = GstSingleBackupUtil.execute_instance_backup(full_bak_cmd, self._os_user, fail_raise=False)
            else:
                full_bak_cmd = cmd_format("python3 {} -t full-backup -A {} -D {} -B {} -P", zbackup_file, gsdb_home,
                                          gsdb_data, bak_dir)
                ret, out = GstSingleBackupUtil.execute_instance_backup_with_pwd(
                    full_bak_cmd, self._os_user, sys_pwd, fail_raise=False)
            if not ret:
                LOGGER.error("Execute full backup command failed, result: %s.", out)
                if not GstZbackupUtil.handle_backup_sed_issue(
                        self._os_user, out, back_dir=bak_dir, gsdb_home=gsdb_home, gsdb_data=gsdb_data):
                    raise ErrCodeException(ErrorCode.FAILED_EXECUTE_COMMAND, *[full_bak_cmd, out],
                                           message="Execute full backup command failed.")
            bak_time_dict = GstSingleBackupUtil.get_backup_time_info(
                self._os_user, bak_dir, enable_sysdba_login=enable_login, sys_pwd=sys_pwd)
        finally:
            clear(sys_pwd)
        return bak_time_dict

    def handle_log_backup(self, backup_dir) -> dict:
        gsdb_home = GstSingleCommonUtil.get_val_of_gsdb_env_var(self._os_user, "GSDB_HOME")
        gsdb_data = GstSingleCommonUtil.get_val_of_gsdb_env_var(self._os_user, "GSDB_DATA")
        app_info = GstSingleParamUtil.get_applications(self.param_dict)
        cache_path = GstSingleParamUtil.get_repository_paths_for_backup(
            self.param_dict, RepositoryDataTypeEnum.CACHE_REPOSITORY.value)[0]
        depend_copy_time_info = GstSingleBackupUtil.get_depend_copy_time_info(
            app_info, self.job_id, self.sub_job_id, cache_path
        )
        # 查询依赖全量副本的完成时间
        full_bak_time = depend_copy_time_info.get(GstSingleKeyConst.LOG_BAK_START_TIME)
        zbackup_file = os.path.realpath(os.path.join(gsdb_home, "bin", "zbackup.py"))
        GstSingleCommonUtil.check_params(
            check_users=[self._os_user], check_paths=[zbackup_file, gsdb_data],
            check_path_owners=[(zbackup_file, [self._os_user])])
        enable_login, sys_pwd = GstSingleBackupUtil.get_enable_sysdba_login_info(self.pid, self.param_dict)
        try:
            # 获取当前时间作为日志备份结束时间
            log_bak_end_time = GstSingleBackupUtil.get_gsdb_now_time(
                self._os_user, enable_sysdba_login=enable_login, sys_pwd=sys_pwd)
            # 日志备份开始、结束时间需要切换成UTC时间
            log_s_utc_time = GstSingleCommonUtil.convert_time_to_utc_time(full_bak_time)
            log_e_utc_time = GstSingleCommonUtil.convert_time_to_utc_time(log_bak_end_time)
            if enable_login:
                log_bak_cmd = cmd_format("python3 {} -t archivelog-backup -A {} -D {} -I {}",
                                         zbackup_file, gsdb_home, gsdb_data, backup_dir)
                log_bak_cmd = f"{log_bak_cmd} -S \"{log_s_utc_time}\" -E \"{log_e_utc_time}\""
                GstSingleBackupUtil.execute_instance_backup(log_bak_cmd, self._os_user)
            else:
                log_bak_cmd = cmd_format("python3 {} -t archivelog-backup -A {} -D {} -I {} -P",
                                         zbackup_file, gsdb_home, gsdb_data, backup_dir)
                log_bak_cmd = f"{log_bak_cmd} -S \"{log_s_utc_time}\" -E \"{log_e_utc_time}\""
                GstSingleBackupUtil.execute_instance_backup_with_pwd(log_bak_cmd, self._os_user, sys_pwd)
        finally:
            clear(sys_pwd)
        bak_info_dict = dict()
        bak_info_dict[ParamKeyConst.COPY_BEGIN_TIME] = depend_copy_time_info.get(
            GstSingleKeyConst.PRE_COPY_END_TIMESTAMP)
        bak_info_dict[ParamKeyConst.COPY_END_TIME] = GstSingleCommonUtil.convert_backup_time_to_timestamp(
            log_bak_end_time)
        bak_info_dict[GstSingleKeyConst.LOG_BAK_START_TIME] = full_bak_time
        bak_info_dict[GstSingleKeyConst.LOG_BAK_END_TIME] = log_bak_end_time
        return bak_info_dict

    def handle_abort_backup_job(self):
        GstSingleCommonUtil.kill_agent_job_process(self.pid, self.job_id)
